#include <iostream>
#include <vector>
#include <string>
#include <cstdio>
#include <cassert>
#include <cstdlib>
#include <ctime>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

#define PROC_NUM 5
#define SEND_TIMES 20

typedef void(*func_t)();//等价于typedef void(*)() func_t

class subProc
{
public:
    subProc(pid_t id, int writeFd)
    : _id(id), _writeFd(writeFd)
    {
        char buffer[1024];
        snprintf(buffer, sizeof buffer, "process[%d]pid[%d]writeFd[%d]", num++, id, writeFd);
        _name = buffer;
    }
public:
    string _name;
    pid_t _id;
    int _writeFd;
    static int num;
};
int subProc::num = 1;

void printInfo()
{
    printf("print information...\n");
}

void addInfo()
{
    printf("add information...\n");
}

void loadPage()
{
    printf("load page...\n");
}

void refresh()
{
    printf("refresh...\n");
}

void playVideo()
{
    printf("playVideo...\n");
}

void setRandSeed()
{
    srand((unsigned int)time(nullptr));
}

void loadFuncs(vector<func_t>& funcs)
{
    funcs.push_back(printInfo);
    funcs.push_back(addInfo);
    funcs.push_back(loadPage);
    funcs.push_back(refresh);
    funcs.push_back(playVideo);
}

void createSub(vector<subProc>& subs, vector<func_t>& funcs)
{
    vector<int> deleteFd;//存放当前子进程继承的写端描述符
    for (int i = 0; i < PROC_NUM; i++)
    {
        int fds[2];
        int n = pipe(fds);
        assert(n == 0);
        (void)n;
        pid_t id = fork();
        if (id == 0)
        {
            /*
            从第二个子进程开始 总是会继承之前所有管道的写端 即父进程的所有写端
            为了避免出现关闭当前写端 对应读端却不能及时关闭的情况(因为还有子进程指向当前管道的写端)
            每次统一关闭继承得来的写端
            */
            for (size_t i = 0; i < deleteFd.size(); i++)
            {
                close(deleteFd[i]);
            }
            close(fds[1]);
            //child to do
            while (true)
            {
                int funcIndex;
                ssize_t n = read(fds[0], &funcIndex, sizeof funcIndex);//阻塞读取管道数据
                if (n == 0)//写端关闭 读端最后必然读到0 子进程退出
                    break;
                assert(n == sizeof funcIndex);
                (void)n;
                funcs[funcIndex]();
            }
            exit(0);
        }
        close(fds[0]);
        //每次创建一个管道及子进程 就把管道写端放入vector 下次创建管道和子进程时会继承之前所有管道的写端 vector内就是所有继承的写端
        deleteFd.push_back(fds[1]);
        subProc sub(id, fds[1]);
        subs.push_back(sub);
    }
}

void sendIndex(vector<subProc>& subs, vector<func_t>& funcs, int count)
{
    int forever = count <= 0 ? 1 : 0;//count(SEND_TIMES)大于0则发送count次任务 count小于等于0则不停发送任务
    while (count > 0 || forever)
    {
        int funcIndex = rand() % funcs.size();
        int procIndex = rand() % PROC_NUM;
        cout << "server" << "[" << getpid() << "]" << "->" << subs[procIndex]._name << " ";
        fflush(stdout);
        ssize_t n = write(subs[procIndex]._writeFd, &funcIndex, sizeof funcIndex);//向管道写入任务码
        assert(n == sizeof funcIndex);
        (void)n;
        if (!forever)
            count--;
        sleep(1);//读快写慢 等待对应子进程执行完任务再次派发任务给随机子进程
    }
}

int main()
{
    vector<func_t> funcs;//存放任务下标(任务码)
    vector<subProc> subs;//存放管道及子进程部分抽象而来的对象
    
    setRandSeed();//利用时间戳设置随机起点 保证程序每次运行时生成的随机种子不同
    
    loadFuncs(funcs);//加载任务列表
    
    //创建子进程 让每个子进程等待接收任务码 收到任务码后执行对应任务
    createSub(subs, funcs);
    
    //随机分配任务 随机选择子进程执行该任务
    sendIndex(subs, funcs, SEND_TIMES);//发送任务码(任务下标)
    
    //关闭父进程的所有写端 依次等待回收子进程
    for (int i = 0; i < PROC_NUM; i++)
    {
        close(subs[i]._writeFd);
        //如果前面没有及时关闭每个子进程继承的写端 读端(子进程)会一直阻塞等待不退出 所以也会导致这里阻塞等待
        //因为前面解决了子进程继承写端的问题 所以当父进程写端关闭 子进程会将管道剩余数据读完后退出
        waitpid(subs[i]._id, nullptr, 0);
    }
    return 0;
}